﻿using System;
using System.Disposables;
using System.Linq;
using System.Windows.Threading;
using SoftSource.Reactive;
using SoftSource.Timecard.Web.Data;
using SoftSource.Timecard.Web.Services;

namespace SoftSource.Timecard.Model
{
    public sealed partial class AdministratorModel : IModel
    {
        #region Fields
        private CompositeDisposable compositeDisposable;
        private Dispatcher dispatcher;

        private UserContext users;
        private EntryContext entries;
        private ProjectContext projects;
        private UtilityContext utilities;
        private StaticContext staticTypes;
        private CustomerContext customers;
        private CategoryContext categories;
        private MilestoneContext milestones;
        private ReadOnlyContext readOnlyTypes;
        private InvoicingContext invoicingContext;
        #endregion Fields

        public AdministratorModel(Dispatcher dispatcher)
        {
            this.dispatcher = dispatcher;
            this.compositeDisposable = new CompositeDisposable();

            InitializeContexts();
            InitializeEntityWrappers();
            InitilizeRefreshHandlers();

            this.PreLoadQuery = Observable.ForkJoin(
                this.LoadStaticTypes(),
                this.LoadReadOnlyTypes())
                .Once();
            // TODO LoadReadWrite
            this.LoadDataQuery = this.LoadReadWriteTypes().Once();
        }

        #region Initialization
        private void InitializeContexts()
        {
            this.users = new UserContext();
            this.entries = new EntryContext();
            this.projects = new ProjectContext();
            this.utilities = new UtilityContext();
            this.staticTypes = new StaticContext();
            this.customers = new CustomerContext();
            this.categories = new CategoryContext();
            this.milestones = new MilestoneContext();
            this.readOnlyTypes = new ReadOnlyContext();
            this.invoicingContext = new InvoicingContext();
        }

        private void InitilizeRefreshHandlers()
        {
            var refreshProjects = from p in this.Projects
                                  from refresh in Observable.ForkJoin(
                                      this.ProjectSummaries.Refresh,
                                      this.InvoicingProjects.Refresh)
                                  select refresh;

            var refreshProjectMembers = from pm in this.ProjectMembers
                                        from pms in this.ProjectMemberSummaries.Refresh
                                        select pms;

            var refreshProjectCategories = from pc in this.ProjectCategories
                                           from pcs in this.ProjectCategorySummaries.Refresh
                                           select pcs;

            var refreshFromInvoiceProjects = from p in this.InvoicingProjects
                                             from refresh in Observable.ForkJoin(
                                                this.ProjectSummaries.Refresh,
                                                this.Projects.Refresh)
                                             select refresh;

            var refreshUsers = from u in this.Users
                               from us in this.UserSummaries.Refresh
                               from managers in Observable.Start(this.SetResources)
                               select managers;

            var refreshCustomers = from c in this.Customers
                                   from cs in this.CustomerSummaries.Refresh
                                   select cs;

            var refreshCustomerUsers = from cu in this.CustomerUsers
                                       from cus in this.CustomerUserSummaries.Refresh
                                       select cus;

            var refreshCustomerProjects = from cp in this.CustomerProjects
                                          from cps in this.CustomerProjectSummaries.Refresh
                                          select cps;

            var refreshMilestones = from m in this.Milestones
                                    from ms in this.MilestoneSummaries.Refresh
                                    select ms;

            var refreshCategories = from c in this.Categories
                                    from cs in this.CategorySummaries.Refresh
                                    select cs;

            this.compositeDisposable.Add(refreshProjects.Subscribe());
            this.compositeDisposable.Add(refreshProjectMembers.Subscribe());
            this.compositeDisposable.Add(refreshProjectCategories.Subscribe());
            this.compositeDisposable.Add(refreshFromInvoiceProjects.Subscribe());
            this.compositeDisposable.Add(refreshUsers.Subscribe());
            this.compositeDisposable.Add(refreshCustomers.Subscribe());
            this.compositeDisposable.Add(refreshCustomerUsers.Subscribe());
            this.compositeDisposable.Add(refreshCustomerProjects.Subscribe());
            this.compositeDisposable.Add(refreshMilestones.Subscribe());
            this.compositeDisposable.Add(refreshCategories.Subscribe());
        }
        #endregion Initialization

        #region Data Load
        private IObservable<Unit> LoadStaticTypes()
        {
            // Static Types
            return Observable.ForkJoin(
                this.Roles.Load,
                this.Reports.Load,
                this.UserTypes.Load,
                this.ProjectTypes.Load,
                this.RevenueTypes.Load,
                this.BillingStates.Load,
                this.ProjectStatuses.Load,
                this.BillingFrequencies.Load)
                .ToUnit();
        }

        private IObservable<Unit> LoadReadOnlyTypes()
        {
            // Read-Only types
            return Observable.ForkJoin(
                this.UserSummaries.Load,
                this.ProjectSummaries.Load,
                this.CategorySummaries.Load,
                this.MilestoneSummaries.Load,
                this.CustomerUserSummaries.Load,
                this.ProjectMemberSummaries.Load,
                this.ProjectCategorySummaries.Load,
                this.CustomerProjectSummaries.Load)
                .ToUnit();
        }

        private IObservable<Unit> LoadReadWriteTypes()
        {
            // Read-Write
            return Observable.ForkJoin(
                this.Customers.Load,
                this.Categories.Load,
                this.Milestones.Load,
                this.CustomerUsers.Load,
                this.ProjectMembers.Load,
                this.CustomerProjects.Load,
                this.InvoicingProjects.Load,
                this.ProjectCategories.Load)
                .ToUnit();
        }
        #endregion Data Load

        #region Set Resources
        private void SetResources()
        {
            var managers = this.readOnlyTypes.UserSummaries.Where(us => us.IsActive && (us.RoleID == Role.Administrator || us.RoleID == Role.ProjectManager));

            this.SetResource("Managers", managers);

            this.SetStaticResources();
            this.SetReadOnlyResources();
        }

        private void SetStaticResources()
        {
            //Static Types
            this.SetResource("Roles", this.staticTypes.Roles);
            this.SetResource("UserTypes", this.staticTypes.UserTypes);
            this.SetResource("ProjectTypes", this.staticTypes.ProjectTypes);
            this.SetResource("RevenueTypes", this.staticTypes.RevenueTypes);
            this.SetResource("BillingStates", this.staticTypes.BillingStates);
            this.SetResource("ProjectStatuses", this.staticTypes.ProjectStatus);
            this.SetResource("BillingFrequencies", this.staticTypes.BillingFrequencies);
        }

        private void SetReadOnlyResources()
        {
            //Read-Only Types
            this.SetResource("UserSummaries", this.readOnlyTypes.UserSummaries);
            this.SetResource("ProjectSummaries", this.readOnlyTypes.ProjectSummaries);
            this.SetResource("CategorySummaries", this.readOnlyTypes.CategorySummaries);
            this.SetResource("CustomerSummaries", this.readOnlyTypes.CustomerSummaries);
            this.SetResource("MilestoneSummaries", this.readOnlyTypes.MilestoneSummaries);
            this.SetResource("CustomerUserSummaries", this.readOnlyTypes.CustomerUserSummaries);
            this.SetResource("ProjectMemberSummaries", this.readOnlyTypes.ProjectMemberSummaries);
            this.SetResource("ProjectCategorySummaries", this.readOnlyTypes.ProjectCategorySummaries);
            this.SetResource("CustomerProjectSummaries", this.readOnlyTypes.CustomerProjectSummaries);
        }

        private void SetResource(string key, object value)
        {
            this.dispatcher.BeginInvoke(() =>
                {
                    if (App.Current.Resources[key] != null)
                        App.Current.Resources.Remove(key);
                    App.Current.Resources.Add(key, value);
                });
        }
        #endregion Set Resources

        #region Load Queries
        private IObservable<Unit> PreLoadQuery { get; set; }
        private IObservable<Unit> LoadDataQuery { get; set; }
        #endregion Load Queries

        #region IDisposable Members

        public void Dispose()
        {
            this.compositeDisposable.Dispose();
            GC.SuppressFinalize(this);
        }

        #endregion

        ~AdministratorModel()
        {
            this.Dispose();
        }
    }
}
